#pragma once

#include <zenox/memory.hpp>
#include <zenox/functional.hpp>
#include <zenox/pragma.hpp>
#include <concepts>
#include <functional>
#include <vector>

namespace zenoui {

template <class Base, class ...Args>
struct Signal {
private:
    struct Observer {
        zenox::weak_ptr<Base> m_that;
        std::function<void(Base *, Args...)> m_func;
    };

    std::vector<Observer> m_observers;

    void _prune_observers() {
        std::erase_if(m_observers, [] (Observer &obs) { return !obs.m_that; });
    }

public:
    ZENO_HOT_FN void operator()(Args ...args) const {
        for (auto it = m_observers.begin(), eit = m_observers.end(); it != eit; ++it) {
            if (auto that = it->m_that.get()) [[likely]] {
                it->m_func(that, args...);
            }
        }
    }

    template <std::derived_from<Base> Derived, std::invocable<Derived *, Args...> Func>
    void connect(zenox::weak_ptr<Derived> that, Func f) {
        m_observers.emplace_back(std::move(that), [f = std::move(f)] (Base *that, auto &&...args) -> decltype(auto) {
            return std::invoke(f, static_cast<Derived *>(that), std::forward<Args>(args)...);
        });
        _prune_observers();
    }

    template <std::derived_from<Base> Derived, std::invocable<Args...> Func>
        requires (!std::invocable<Func, Derived, Args...>)
    void connect(zenox::weak_ptr<Derived> that, Func f) {
        m_observers.emplace_back(std::move(that), [f = std::move(f)] (Base *, auto &&...args) -> decltype(auto) {
            return std::invoke(f, std::forward<Args>(args)...);
        });
        _prune_observers();
    }
};

}
